package com.xpalive.utils.lambda;

import static com.xpalive.commons.StringPool.EMPTY;
import static com.xpalive.commons.StringPool.GET_PREFIX;
import static com.xpalive.commons.StringPool.IS_PREFIX;

import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;

/*
    方法引用有很多种，它们的语法如下：

    静态方法引用：ClassName::methodName
    实例上的实例方法引用：instanceReference::methodName
    超类上的实例方法引用：super::methodName
    类型上的实例方法引用：ClassName::methodName
    构造方法引用：Class::new
    数组构造方法引用：TypeName[]::new
*/

//com.baomidou.mybatisplus.core.toolkit.LambdaUtils
public class XLambdaUtils {

    /**
     * SerializedLambda 反序列化缓存 copy from com.baomidou.mybatisplus.core.toolkit.LambdaUtils
     */
    @Getter
    private static final Map<String, WeakReference<SerializedLambda>> FUNC_CACHE = new ConcurrentHashMap<>();
    @Getter
    private static final Map<String, WeakReference<String>> PROPERTY_CACHE = new ConcurrentHashMap<>();


    public static <T> String getFieldName(XFieldNameFunction<T, ?> lambda) {
        String canonicalName = lambda.getClass().getCanonicalName();
        return PROPERTY_CACHE.computeIfAbsent(canonicalName, key -> {
            SerializedLambda serializedLambda = getSerialLambda(lambda);
            String getter = serializedLambda.getImplMethodName();
            String fieldName = Introspector.decapitalize(
                getter.startsWith(GET_PREFIX) ? getter.replace(GET_PREFIX, EMPTY)
                    : getter.replace(IS_PREFIX, EMPTY)
            );
            return new WeakReference<>(fieldName);
        }).get();
    }

    public static <T> SerializedLambda getSerialLambda(XFieldNameFunction<T, ?> lambda) {
        String canonicalName = lambda.getClass().getCanonicalName();
        return FUNC_CACHE.computeIfAbsent(canonicalName, key -> {
            try {
                Method method = lambda.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                SerializedLambda serialLambda = (SerializedLambda) method.invoke(lambda);
                return new WeakReference<>(serialLambda);
            } catch (ReflectiveOperationException e) {
                throw new RuntimeException(e);
            }
        }).get();
    }
}
